#include <animation/Animation.h>
#include <animation/AnimationFileFormat.h>
#include <system/Log.h>

namespace ne
{
    Animation::Animation(ResourceManager *pManager,
                         const uint64_t id,
                         const std::string &name,
                         const std::string &fileName,
                         const bool isManual,
                         ManualResourceLoader *pLoader)
        : Resource(pManager, id, name, fileName, isManual, pLoader),
          mLength(0.0f)
    {
    }

    void Animation::setLength(const real len)
    {
        mLength = len;
    }

    real Animation::getLength() const
    {
        return mLength;
    }

    NodeAnimationTrackPtr Animation::createNodeTrack(const uint16_t handle, Node *node)
    {
        if (!hasNodeTrack(handle))
        {
            NodeAnimationTrackPtr pTrack(new NodeAnimationTrack(this, handle, node));
            mNodeTrackMap[handle] = pTrack;
            return pTrack;
        }
        return NodeAnimationTrackPtr();
    }

    size_t Animation::getNumNodeTracks() const
    {
        return mNodeTrackMap.size();
    }

    const NodeAnimationTrackMap& Animation::getNodeTrackMap() const
    {
        return mNodeTrackMap;
    }

    NodeAnimationTrackPtr Animation::getNodeTrack(const uint16_t handle) const
    {
        NodeAnimationTrackMapConstIterator it = mNodeTrackMap.find(handle);
        if (it != mNodeTrackMap.end())
        {
            return it->second;
        }
        return NodeAnimationTrackPtr();
    }

    bool Animation::hasNodeTrack(const uint16_t handle) const
    {
        NodeAnimationTrackMapConstIterator it = mNodeTrackMap.find(handle);
        if (it != mNodeTrackMap.end())
        {
            return true;
        }
        return false;
    }

    void Animation::removeNodeTrack(const uint16_t handle)
    {
        NodeAnimationTrackMapIterator it = mNodeTrackMap.find(handle);
        if (it != mNodeTrackMap.end())
        {
            mNodeTrackMap.erase(it);
        }
    }

    void Animation::removeAllNodeTracks()
    {
        mNodeTrackMap.clear();
    }

    void Animation::removeAll()
    {
        removeAllNodeTracks();
    }

    void Animation::apply(const real time, const real weight, const real scaleU)
    {
        for (NodeAnimationTrackMapIterator it=mNodeTrackMap.begin(); it!=mNodeTrackMap.end(); ++it)
        {
            it->second->apply(time, weight, scaleU);
        }
    }

    void Animation::applyToSkeleton(Skeleton *skeleton, const real time, const real weight, const real scaleU)
    {
        if (skeleton)
        {
            for (NodeAnimationTrackMapIterator it=mNodeTrackMap.begin(); it!=mNodeTrackMap.end(); ++it)
            {
                Joint *joint = skeleton->getJoint(it->first);
                it->second->applyToNode(joint, time, weight, scaleU);
            }
        }
    }

    void Animation::optimise(const bool discardIdentityNodeTracks)
    {
        optimiseNodeTracks(discardIdentityNodeTracks);
    }

    void Animation::optimiseNodeTracks(const bool discardIdentityNodeTracks)
    {
        std::list<uint16_t> tracksToRemove;
        for (NodeAnimationTrackMapConstIterator it=mNodeTrackMap.begin(); it!=mNodeTrackMap.end(); ++it)
        {
            NodeAnimationTrackPtr pTrack = it->second;
            if (discardIdentityNodeTracks && !pTrack->hasNonZeroKeyFrames())
            {
                // Mark the entire track for remove
                tracksToRemove.push_back(it->first);
            }
            else
            {
                pTrack->optimise();
            }
        }

        std::cout << "Removed " << tracksToRemove.size() << " NodeAnimationTracks\n";

        // Now remove the tracks we marked for death
        for (std::list<uint16_t>::iterator i=tracksToRemove.begin(); i!=tracksToRemove.end(); ++i)
        {
            removeNodeTrack(*i);
        }
    }

    Animation::~Animation()
    {
        removeAll();
    }

    bool Animation::loadImplement()
    {
        FILE *file = openFile("rb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Open Animation (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        AnimationHeader animationHeader;
        if (!fread(&animationHeader, sizeof(AnimationHeader), 1, file))
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Animation (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        if (animationHeader.mMagicNum != ANIMATION_FILE_MAGIC_NUMBER ||
            animationHeader.mVersion != ANIMATION_FILE_VERSION)
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Animation (%s) from file (%s) with wrong Magic or Version", getName().c_str(), getFileName().c_str());
            return false;
        }

        setLength(animationHeader.mLength);

        // Seek to NodeTrackHeaders
        fseek(file, animationHeader.mNodeTrackOffset, SEEK_SET);

        // Create NodeTrack
        AnimationTrackHeader animationTrackHeaders[animationHeader.mNumNodeTracks];
        if (!fread(animationTrackHeaders, sizeof(AnimationTrackHeader), animationHeader.mNumNodeTracks, file))
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load AnimationTrack for Animation (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        for (uint32_t i=0; i<animationHeader.mNumNodeTracks; ++i)
        {
            NodeAnimationTrackPtr pTrack = createNodeTrack(animationTrackHeaders[i].mHandle);
            TransformKeyFrameHeader keyFrameHeaders[animationTrackHeaders[i].mNumKeyFrames];

            // Seek to TransformKeyFrame
            fseek(file, animationTrackHeaders[i].mKeyFrameOffset, SEEK_SET);
            if (!fread(keyFrameHeaders, sizeof(TransformKeyFrameHeader), animationTrackHeaders[i].mNumKeyFrames, file))
            {
                closeFile(file);
                Log::Out(LL_WARNING,
                    "Load TransformKeyFrame for Animation (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
                return false;
            }

            for (uint32_t j=0; j<animationTrackHeaders[i].mNumKeyFrames; ++j)
            {
                TransformKeyFrame *pKeyFrame = pTrack->createTransformKeyFrame(keyFrameHeaders[j].mTime);
                pKeyFrame->setTranslate(Vector3(keyFrameHeaders[j].mTrandlate[0],
                                                keyFrameHeaders[j].mTrandlate[1],
                                                keyFrameHeaders[j].mTrandlate[2]));
                pKeyFrame->setRotate(Quaternion(keyFrameHeaders[j].mRotate[0],
                                                keyFrameHeaders[j].mRotate[1],
                                                keyFrameHeaders[j].mRotate[2],
                                                keyFrameHeaders[j].mRotate[3]));
                pKeyFrame->setScale(Vector3(keyFrameHeaders[j].mScale[0],
                                            keyFrameHeaders[j].mScale[1],
                                            keyFrameHeaders[j].mScale[2]));
            }
        }

        closeFile(file);

        optimise();

        return true;
    }

    bool Animation::unloadImplement()
    {
        removeAll();
        return true;
    }

    bool Animation::downloadImplement()
    {
        optimise();

        FILE *file = openFile("wb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Create Animation (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        AnimationHeader animationHeader;
        memset(&animationHeader, 0, sizeof(AnimationHeader));

        animationHeader.mMagicNum = ANIMATION_FILE_MAGIC_NUMBER;
        animationHeader.mVersion = ANIMATION_FILE_VERSION;
        strncpy(animationHeader.mName, mName.c_str(),
                MAX_STRING_LENGTH_IN_ANIMATION_FILE);

        animationHeader.mLength = getLength();
        animationHeader.mNumNodeTracks = getNumNodeTracks();
        fwrite(&animationHeader, sizeof(AnimationHeader), 1, file);
        animationHeader.mNodeTrackOffset = ftell(file);
        fseek(file, 0, SEEK_SET);
        fwrite(&animationHeader, sizeof(AnimationHeader), 1, file);

        AnimationTrackHeader animationTrackHeaders[animationHeader.mNumNodeTracks];
        memset(animationTrackHeaders, 0, sizeof(AnimationTrackHeader) * animationHeader.mNumNodeTracks);
        fwrite(animationTrackHeaders, sizeof(AnimationTrackHeader), animationHeader.mNumNodeTracks, file);

        NodeAnimationTrackMapConstIterator it = mNodeTrackMap.begin();
        for (uint32_t i=0; i<animationHeader.mNumNodeTracks; ++i)
        {
            animationTrackHeaders[i].mKeyFrameOffset = ftell(file);

            NodeAnimationTrackPtr pTrack = it->second;
            if (pTrack.notNull())
            {
                animationTrackHeaders[i].mHandle = pTrack->getHandle();
                animationTrackHeaders[i].mNumKeyFrames = pTrack->getKeyFramesCount();
                _saveNodeTrackToFile(*pTrack, file);
            }

            ++it;
        }

        fseek(file, animationHeader.mNodeTrackOffset, SEEK_SET);
        fwrite(animationTrackHeaders, sizeof(AnimationTrackHeader), animationHeader.mNumNodeTracks, file);

        closeFile(file);

        return true;
    }

    size_t Animation::_calculateSize() const
    {
        return mSize;
    }

    void Animation::_saveNodeTrackToFile(const NodeAnimationTrack &track, FILE *file)
    {
        if (file)
        {
            const KeyFrameVector &keyFrames = track.getKeyFrameVector();
            size_t numKeyFrames = track.getKeyFramesCount();
            TransformKeyFrameHeader keyFrameHeaders[numKeyFrames];
            for (size_t i=0; i<numKeyFrames; ++i)
            {
                TransformKeyFrame *pKeyFrame = dynamic_cast<TransformKeyFrame*>(keyFrames[i]);
                if (pKeyFrame)
                {
                    keyFrameHeaders[i].mTime = pKeyFrame->getTime();

                    const Vector3 &position = pKeyFrame->getTranslate();
                    keyFrameHeaders[i].mTrandlate[0] = position.x;
                    keyFrameHeaders[i].mTrandlate[1] = position.y;
                    keyFrameHeaders[i].mTrandlate[2] = position.z;

                    const Quaternion &rotate = pKeyFrame->getRotate();
                    keyFrameHeaders[i].mRotate[0] = rotate.x;
                    keyFrameHeaders[i].mRotate[1] = rotate.y;
                    keyFrameHeaders[i].mRotate[2] = rotate.z;
                    keyFrameHeaders[i].mRotate[3] = rotate.w;

                    const Vector3 &scale = pKeyFrame->getScale();
                    keyFrameHeaders[i].mScale[0] = scale.x;
                    keyFrameHeaders[i].mScale[1] = scale.y;
                    keyFrameHeaders[i].mScale[2] = scale.z;
                }
            }

            fwrite(keyFrameHeaders, sizeof(TransformKeyFrameHeader), numKeyFrames, file);
        }
    }
}
